﻿using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NewRetail.Application.Components.WeChat.Configurations;
using NewRetail.Application.Components.WeChat.Entity;
using NewRetail.Application.Components.WeChat.Services.HttpClients;
using NewRetail.Application.Core;
using NewRetail.Application.Utility;
using SKIT.FlurlHttpClient.Wechat.Api;
using SKIT.FlurlHttpClient.Wechat.Api.Models;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Uow;

namespace NewRetail.Application.Components.WeChat.Services.BackgroundServices
{
    /// <summary>
    /// AccessToken 的刷新,实现了 AccessToken 的主动刷新,即由系统在后台周期性地执行刷新操作，在需要使用 AccessToken 时直接读取已有的记录即可，无需关心 AccessToken 是否过期。
    /// </summary>
    public class WeChatAccessTokenRefreshingWorker : ApplicationService, ITransientDependency
    {
        #region params
        private readonly ILogger _logger;
        private readonly WeChatOptions _wechatOptions;
        private readonly IUnitOfWorkManager _unitOfWorkManager;
        private readonly RedisClient _redisClient;
        private readonly IWeChatAccessToken _weChatAccessToken;
        private readonly RedLockFactoryWrapper _redLockFactoryWrapper;
        private readonly IWeChatApiHttpClientFactory _wechatApiHttpClientFactory;
        #endregion

        #region 构造
        public WeChatAccessTokenRefreshingWorker(ILogger<WeChatAccessTokenRefreshingWorker> logger, IWeChatAccessToken weChatAccessToken,
            RedisClient redisClient, RedLockFactoryWrapper redLockFactoryWrapper, IUnitOfWorkManager unitOfWorkManager,
            IOptions<WeChatOptions> wechatOptions, IWeChatApiHttpClientFactory wechatApiHttpClientFactory)
        {
            _logger = logger;
            _redLockFactoryWrapper = redLockFactoryWrapper;
            _redisClient = redisClient;
            _unitOfWorkManager = unitOfWorkManager;
            _wechatOptions = wechatOptions.Value;
            _wechatApiHttpClientFactory = wechatApiHttpClientFactory;
            _weChatAccessToken = weChatAccessToken;
        }
        #endregion

        #region method
        [UnitOfWork]
        public virtual async Task Execute()
        {
            try
            {
                _logger.LogInformation(DateTime.Now.ToString() + "[WechatAccessTokenRefresh]");
                IList<Task> tasks = new List<Task>();
                //多个公众平台需要都刷新
                foreach (var wechatAccount in _wechatOptions.Accounts)
                {
                    Task task = TryRefreshWechatAccessTokenAsync(wechatAccount.AppId);
                    tasks.Add(task);
                }

                await Task.WhenAll(tasks);

            }
            catch (Exception e)
            {
                _logger.LogError(e, e.Message);
                throw;
            }
        }
        #endregion

        #region 刷新AccessToken
        [UnitOfWork]
        private async Task TryRefreshWechatAccessTokenAsync(string appId, CancellationToken cancellationToken = default)
        {
            if (string.IsNullOrEmpty(appId))
                return; // 无效参数

            //var redis = _redisClient.Connection.GetDatabase(2);
            //var keyWechat = $"{ConstOptions.KeyWechatRedisPrefix}{appId}";
            ////根据key获取redis里面的值
            //var keyWechatValue = await redis.StringGetAsync(keyWechat);
            //var wechatAccessTokenEntity = keyWechatValue.IsNullOrEmpty ? null : UtilsHelper.DeserializeObject<WechatAccessTokenEntity>(keyWechatValue.ToString());
            
            //获取accessToken对象
            var wechatAccessTokenEntity = await _weChatAccessToken.GetAccessTokenObject(appId);
            if (wechatAccessTokenEntity?.ExpireTimestamp > DateTimeOffset.Now.ToUnixTimeSeconds())
                return; // AccessToken 未过期

            // resource 锁定的对象
            // expiryTime 锁定过期时间，锁区域内的逻辑执行如果超过过期时间，锁将被释放
            // waitTime 等待时间,相同的 resource 如果当前的锁被其他线程占用,最多等待时间
            // retryTime 等待时间内，多久尝试获取一次
            using var distributedLock = await _redLockFactoryWrapper.RedLockFactory.CreateLockAsync(
                resource: $"{nameof(WeChatAccessTokenRefreshingWorker)}",
                expiryTime: TimeSpan.FromMinutes(1),
                waitTime: TimeSpan.FromMinutes(1),
                retryTime: TimeSpan.FromSeconds(3));

            if (distributedLock.IsAcquired)
            {
                var client = _wechatApiHttpClientFactory.Create(appId);
                var request = new CgibinTokenRequest();
                var response = await client.ExecuteCgibinTokenAsync(request, cancellationToken);
                if (!response.IsSuccessful())
                {
                    _logger.LogWarning(
                        "刷新 AppId 为 {0} 微信 AccessToken 失败（状态码：{1}，错误代码：{2}，错误描述：{3}）。",
                        appId, response.RawStatus, response.ErrorCode, response.ErrorMessage
                    );
                    return; // 请求失败
                }
                //提前九十分钟过期，以便于系统能及时刷新，防止因在过期临界点时出现问题
                long nextExpireTimestamp = DateTimeOffset.Now
                        .AddSeconds(response.ExpiresIn)
                        .AddMinutes(-90)
                        .ToUnixTimeSeconds();
                if (wechatAccessTokenEntity == null)
                {
                    wechatAccessTokenEntity = new WechatAccessTokenEntity()
                    {
                        AppId = appId,
                        AccessToken = response.AccessToken,
                        ExpireTimestamp = nextExpireTimestamp
                    };
                    await _weChatAccessToken.SetAccessTokenObject(wechatAccessTokenEntity);
                    //var valueRet = UtilsHelper.SerializeObject<WechatAccessTokenEntity>(wechatAccessTokenEntity);
                    //await redis.StringSetAsync(key: keyWechat, value: valueRet, expiry: TimeSpan.FromHours(1));
                }
                else
                {
                    wechatAccessTokenEntity.AccessToken = response.AccessToken;
                    wechatAccessTokenEntity.ExpireTimestamp = nextExpireTimestamp;

                    await _weChatAccessToken.SetAccessTokenObject(wechatAccessTokenEntity);
                    //var valueRet = UtilsHelper.SerializeObject<WechatAccessTokenEntity>(wechatAccessTokenEntity);
                    //await redis.StringSetAsync(key: keyWechat, value: valueRet, expiry: TimeSpan.FromHours(1));
                }
                _logger.LogInformation("刷新 AppId 为 {0} 的微信 AccessToken 成功。", appId);
            }
            else
            {
                _logger.LogInformation("未能拿到锁，存在并发");
            }
        }
        #endregion
    }
}
